package com.gec.anan.map.service.impl;


import com.gec.anan.common.constant.RedisConstant;
import com.gec.anan.common.constant.SystemConstant;
import com.gec.anan.common.result.Result;
import com.gec.anan.common.util.LocationUtil;
import com.gec.anan.driver.client.DriverInfoFeignClient;
import com.gec.anan.map.repository.OrderServiceLocationRepository;
import com.gec.anan.map.service.LocationService;
import com.gec.anan.model.entity.driver.DriverSet;
import com.gec.anan.model.entity.map.OrderServiceLocation;
import com.gec.anan.model.form.map.OrderServiceLocationForm;
import com.gec.anan.model.form.map.SearchNearByDriverForm;
import com.gec.anan.model.form.map.UpdateDriverLocationForm;
import com.gec.anan.model.form.map.UpdateOrderLocationForm;
import com.gec.anan.model.vo.map.NearByDriverVo;
import com.gec.anan.model.vo.map.OrderLocationVo;
import com.gec.anan.model.vo.map.OrderServiceLastLocationVo;
import com.gec.anan.order.client.OrderInfoFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private DriverInfoFeignClient driverInfoFeignClient;

    @Autowired
    private OrderServiceLocationRepository orderServiceLocationRepository;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    //更新司机的信息
    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        //司机经纬度信息
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(),
                updateDriverLocationForm.getLatitude().doubleValue());
        //司机开始接单，把坐标位置上传到redis的geo中
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION,
                point,
                updateDriverLocationForm.getDriverId().toString());

        return true;
    }

    //删除司机信息
    @Override
    public Boolean removeDriverLocation(Long driverId) {
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION,
                driverId.toString());
        return true;
    }

    //搜索乘客方圆xkm匹配的坐标（司机的位置）
    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
                //1.
        // 获取坐标位置
        // 搜索经纬度位置5公里以内的司机
        //定义经纬度点
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());
        //定义距离：5公里(系统配置)
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
        //定义以point点为中心，distance为距离这么一个范围
        RedisGeoCommands.GeoRadiusCommandArgs args =
                RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                        .includeDistance()//携带距离数据
                        .includeCoordinates()//携带坐标数据
                        .sortAscending();//升序

        //包含坐标和设定的距离
        Circle circle = new Circle(point, distance);

        //从redis的geo中拿出数据
        GeoResults<RedisGeoCommands.GeoLocation<String>> radius = redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION,
                circle,
                args);
        //2.返回list集合(匹配到的数据有一个或者多个)
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> content = radius.getContent();
        //3.遍历每个司机的数据，根据司机的status来判断是否接单(1-接单，0-不接单)
        List<NearByDriverVo> list = new ArrayList<>();
        if (!CollectionUtils.isEmpty(content)) {
            //遍历数据
            for (GeoResult<RedisGeoCommands.GeoLocation<String>> item : content) {
                Long driverId = Long.parseLong(item.getContent().getName());

                Result<DriverSet> driverSetResult = driverInfoFeignClient.getDriverSet(driverId);
                DriverSet driverSet = driverSetResult.getData();

                //当前司机与乘客的距离
                BigDecimal currentDistance = new BigDecimal(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
                //司机接单条件(司机可以手动设置)
                //条件1，你离我太远不接
                if (driverSet.getAcceptDistance().doubleValue() != 0 &&
                        driverSet.getAcceptDistance().subtract(currentDistance)
                                .doubleValue() < 0) {
                    continue;
                }
                //司机接单的条件
                //条件2，总里程少于我设定的不接
                //acceptDistance==0;什么都接
                if (driverSet.getOrderDistance().doubleValue() != 0 &&
                        driverSet.getOrderDistance().subtract(searchNearByDriverForm.getMileageDistance())
                                .doubleValue()<0) {
                    continue;
                }
                //不满足条件的司机不在地图上显示，剩下的装到list中
                NearByDriverVo nearByDriverVo = new NearByDriverVo();
                nearByDriverVo.setDriverId(driverId);
                nearByDriverVo.setDistance(currentDistance);
                list.add(nearByDriverVo);

            }
        }
        return list;
    }

    //更新司机的实时位置到redis中
    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        //存入redis中
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(), orderLocationVo);
        return true;
    }
    //乘客获取司机端位置信息
    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        //从redis中取出司机的信息位置
        OrderLocationVo orderLocationVo = (OrderLocationVo)redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
        return orderLocationVo;
    }

    //mongodb的位置信息
    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        //1.list集合存储
        ArrayList<OrderServiceLocation> list = new ArrayList<>();
        //2.遍历信息，存入list集合
        orderLocationServiceFormList.forEach(item->{
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(item, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());

            list.add(orderServiceLocation);
        });

        //3.保存全部
        orderServiceLocationRepository.saveAll(list);

        return true;
    }

    //乘客端获取最后一个信息位置
    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        //1.从mongodb中查询
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.with(Sort.by(Sort.Order.desc("createTime")));
        //2.获取最后一个位置
        query.limit(1);

        //3.mongodb查询
        OrderServiceLocation locationVo = mongoTemplate.findOne(query, OrderServiceLocation.class);

        //4.返回的vo对象
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(locationVo, orderServiceLastLocationVo);

        return orderServiceLastLocationVo;
    }

    //计算实际的里程
    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //1.根据订单id获取司机的位置，list集合，升序排序
        //查询MongoDB
        List<OrderServiceLocation> list = orderServiceLocationRepository
                .findByOrderIdOrderByCreateTimeAsc(orderId);

        //2.遍历集合，计算多个位置点距离的总和

        double realDistance = 0;
        if(!CollectionUtils.isEmpty(list)){
            for (int i = 0,size=list.size()-1; i <size ; i++) {
                OrderServiceLocation location1 = list.get(i);
                OrderServiceLocation location2 = list.get(i + 1);

                double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(),
                        location1.getLongitude().doubleValue(),
                        location2.getLatitude().doubleValue(),
                        location2.getLongitude().doubleValue());
                realDistance +=distance;
            }
        }
        //TODO 开发无法难以测出实际距离，所以模拟数据
        if(realDistance == 0) {
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal("5"));
        }

        //3.返回实际里程

        return new BigDecimal(realDistance);
    }

}
